클래스 템플릿(Class Template)템플릿을 이용해서 제네릭 함수를 만들 수 있다.
동일하게 템플릿을 사용해서 제네릭 클래스를 생성할 수 있다.
클래스에서 데이터 멤버의 타입을 매개변수화 할 수 있다.
컨테이너에서 많이 사용됨
컨테이너STL에서 제공하는 벡터가 아닌 선형 대수에서 사용하는 제네릭 벡터 클래스
template <typename T>
class vector{
public:
explicit vector(int size): my_size(size), data(new T[my_size]){}
vector(const vector& that): my_size(taht.my_size), data(new T[my_size]);{
std::copy(&that.data[0], &that.data[that.my_size], &data[0]);
}
int size(void) const{ return my_size; }
const T& operator[](int i) const{
check_index(i);
return data[i];
}
private:
int my_size;
std::unique_ptr<T[]> data;
};
템플릿 매개변수 또한 기본값으로 설정이 가능하다.
struct row_major{};
struct col_major{};
struct heap{};
struct stack{};
template <typename T=double, typename Orientation=col_major, typename Where=heap>
class vector{};
int main(void){
vector<float, row_major, heap> v1;
vector<float, row_major> v1;
vector<> w;
}
default값은 사용할 때, 마지막 인수만 생략이 가능하다.
두 번째 인수가 기본값이고, 마지막 인수가 아닌 경우 두 인수 모두 작성해 주어야 함
템플릿의 기본값은 함수 인수의 기본값 이외에 앞의 매개변수에서 참조할 수 있다.
template <typename T, typename U=T>
class pair{};
int main(void){
pair<int, float> p1;
pair<int> p2;
}
유니폼 클래스 및 함수 인터페이스 디자인함수 템플릿을 먼저 작성한 뒤 해당 메서드를 구현해 클래스에서 적용하거나
클래스의 인터페이스를 먼저 개발한 뒤 이 인터페이스에 대한 제네릭 함수를 구현할 수 있다.
제네릭 함수가 표준 라이브러리에서 내장 타입이나 클래스를 처리해야 할 경우,
클래스를 변경할 수 없기 때문에 인터페이스에 함수를 적용해야 한다.
타입에 따라서 함수 동작이 달라지는 특수화와 메타프로그래밍의 경우 선택 사항이 있다.
#include <iostream>
using namespace std;
template <typename T>
T sum(const T* array, int n){
T sum(0);
for(int i=0; i<n; ++i) sum+=array[i];
return sum;
}
int main(void){
int ai[]={2, 4, 7};
double ad[]={2., 4.5, 7.};
cout<<"sum ai is "<<sum(ai, 3)<<'\n';
cout<<"sum ad is "<<sum(ad, 3)<<'\n';
return 0;
}
위의 코드에서 배열의 크기를 인자로 넘겨주어야 한다.
아래와 같이 코드를 수정하면, 컴파일러가 인자의 크기를 추론할 수 있다.
#include <iostream>
template <typename T, unsigned N>
T sum(const T (&array)[N]){
T sum(0);
for(int i=0; i<N; ++i) sum+=array[i];
return sum;
}
int main(void){
int ai[]={2, 4, 7};
std::cout<<"sum ai is "<<sum(ai)<<'\n';
return 0;
}
위의 코드와 같이 템플릿으로 배열의 크기를 추론할 경우,
동일한 타입이면서 서로 다른 크기를 갖는 두개의 배열에 대하여 각기 다른 함수로 인스턴스화 된다.
(위와 같이 짧은 함수의 경우 함수가 인라인되기 때문에 실행파일의 크기에는 영향을 미치지 않을 것임)
리스트에 있는 요소들의 합리스트는 요소에 값과 다음 요소의 레퍼런스를 포함하는 단순한 자료 구조이다.
C++11의 표준 라이브러리의 std::list는 이중 연결 리스트이며,
이전 요소의 레퍼런스가 없는 std::forward_list도 존재한다.
#include <iostream>
template <typename T>
struct list_entry{
list_entry(const T& value): value(value), next(0){}
T value;
list_entry<T>* next;
};
template <typename T>
struct list{
list(): first(0), last(nullptr){}
~list(){
while(first){
list_entry<T>* tmp=first->next;
delete first;
first=tmp;
}
}
void append(const T& x){
last=(first?last->next:first)=new list_entry<T>(x);
}
list_entry<T> *first, *last;
};
template <typename T>
T sum(const list<T>& l){
T sum=0;
for(auto entry=l.first; entry!=nullptr; entry=entry->next) sum+=entry->value;
return sum;
}
int main(void){
list<float> l;
l.append(2.0f); l.append(4.0f); l.append(7.0f);
float ss=sum(l);
std::cout<<"sum of List is "<<ss<<'\n';
}
공통으로 사용할 수 있는 인터페이스 구현을 목표로 할 경우
동일한 함수의 구현이 비슷해야 한다.
배열의 합을 구하는 sum과 리스트의 합을 구하는 sum의 구현은
- 서로 다른 방법으로 값에 접근
- 서로 다른 방법으로 요소를 순회
- 서로 다른 종료 기준을 갖음
서로 다른점이 많다. 하지만 추상적인 기준으로 보면 두 함수는 동일한 작업을 진행한다.
- 데이터 접근
- 다음 요소로 진행
- 종료 확인
두 타입(배열 & 리스트)에 대하여 하나의 제네릭 함수를 제공하기 위해서는 공통의 인터페이스를 구축하여야 한다.
배열의 합을 구하는 방법(대안)위의 배열을 구하는 sum 함수는 메모리에 무작위로 데이터가 분산되어 있는 리스트에는 적용할 수 없는
인덱스 지향 스타일로 배열에 접근하였다.
이를 배열의 처음과 끝을 가르키는 포인터를 이용해서 구현할 수 있다.
#include <iostream>
template <typename T>
inline T accumulate_array(T* a, T* a_end){
T sum(0);
for(; a!=a_end; ++a) sum+=*a;
return sum;
}
int main(void){
int ai[]={2, 4, 7};
double ad[]={2., 4.5, 7.};
cout<<"sum ai is "<<accumulate_array(ai, &ai[3])<<'\n';
cout<<"sum ad is "<<accumulate_array(ad, ad+3)<<'\n';
}
위와 같이 오른쪽 개구간을 나타내는 한쌍의 포인터를 범위(Range)라고 한다.표준 라이브러리에서 많은 알고리즘은 포인터와 같은 개체의 범위를 위와 같이 두 개의 포인터로 구현한다.
모든 컨테이너 타입과 기존 배열을 위한 공통 인터페이스를 도입하는 하기 위해서반복자(iterator)라고 하는 일반화된 포인터를 이용한다.
- ++it를 사용해 시퀀스를 순회
- *it를 사용해 값에 접근
- ==이나 !=를 사용해 반복자를 비교
#include <iostream>
template <typename T>
struct list_entry{
list_entry(const T& value): value(value), next(0){}
T value;
list_entry<T>* next;
};
template <typename T>
struct list_iterator{
using valuetype=T;
list_iterator(list_entry<T>* entry): entry(entry){}
T& operator*(){ return entry->value; }
const T& operator*(void) const{ return entry->value; }
list_iterator<T>& operator++(void){ entry=entry->next; return *this; }
bool operator!=(const list_iterator<T>& other) const{
return entry!=other.entry;
}
list_entry<T>* entry;
};
template <typename T>
struct list{
list(): first(0), last(nullptr) {}
~list(){
while(first){
list_entry<T>* tmp=first->next;
delete first;
first=tmp;
}
}
void append(const T& x){
last=(first?last->next:first)=new list_entry<T>(x);
}
list_iterator<T> begin(){ return list_iterator<T>(first); }
list_iterator<T> end(){ return list_iterator<T>(0); }
list_entry<T> *first, *last;
};
template <typename Iter, typename T>
inline T accumulate(Iter it, Iter end, T init){
for(; it!=end; ++it) init+=*it;
return init;
}
int main(void){
int a[]={2, 4, 6, 8, 10};
list<float> l;
l.append(2.0f); l.append(4.0f); l.append(6.0f);
l.append(8.0f); l.append(10.0f);
std::cout<<"array sum="<<accumulate(a, a+5, 0.0)<<'\n';
std::cout<<"list sum="<<accumulate(l.begin(), l.end(), 0)<<'\n';
return 0;
}
사전증가 & 사후증가operator++를 통해서 list_iterator의 증가 연산자를 정의해 주었다.
사전증가는 entry 멤버를 갱신하고, 반복자를 가르키는 레퍼런스(list_iterator<T>&)를 반환한다.
사후증가는 이전 값을 반환하고, 다음번 반복자를 사용할 때 다음 리스트 요소를 참조하도록
내부 상태를 증가시킨다.
(사후증가를 구현하기 위해서 멤버 데이터를 변경하기 전에 반복자 전체를 복사하고, 해당 복사본을 반환한다.)
template <typename T>
struct list_iterator{
list_iterator<T> operator++(int){
list_iterator<T> tmp(*this);
p=p->next;
return tmp;
}
}